package lab.cadl.lirui.markdown.core.utils

import com.codewaves.codehighlight.core.Highlighter
import com.codewaves.codehighlight.core.StyleRenderer
import com.codewaves.codehighlight.core.StyleRendererFactory
import com.codewaves.codehighlight.renderer.HtmlRenderer
import lab.cadl.lirui.markdown.core.exceptions.MarkdownException
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.*


enum class HlTokenType {
    NORMAL,
    KEYWORD,
    FUNCTION,
    TITLE,
    PARAMS,
    STRING,
    NUMBER,
    COMMENT,
    CLASS;

    companion object {
        val LOG: Logger = LoggerFactory.getLogger(HlTokenType::class.java)

        fun fromString(style: String?): HlTokenType {
            if (style == null) {
                return NORMAL
            }

            val s = if (style.startsWith("meta-")) {
                style.substring(5)
            } else {
                style
            }

            if (s.toUpperCase() in values().map { it.toString() }) {
                return valueOf(s.toUpperCase())
            }

            return when (s) {
                "function" -> FUNCTION
                "class" -> CLASS
                "keyword" -> KEYWORD
                "comment" -> COMMENT
                else -> {
                    LOG.info("style {} as normal token", style)
                    NORMAL
                }
            }
        }
    }
}

open class HlToken(val type: HlTokenType, val content: String)

class HlNormal(s: String) : HlToken(HlTokenType.NORMAL, s)

class HlLine(val indent: String, val tokens: List<HlToken>)

class HlCodeBlock(val lines: List<HlLine>)

class HlRenderer : HtmlRenderer("hl-") {
    private val typeStack: Stack<HlTokenType> = Stack()
    private val lines: MutableList<HlLine> = Stack()
    private var currentTokens: MutableList<HlToken> = mutableListOf()

    override fun onPushSubLanguage(name: String?, code: CharSequence?) {
        super.onPushSubLanguage(name, code)
    }

    override fun onPopStyle() {
        super.onPopStyle()

        typeStack.pop()
    }

    override fun onPushStyle(style: String?) {
        super.onPushStyle(style)

        typeStack.push(HlTokenType.fromString(style))
    }

    override fun onPushCodeBlock(codeLexeme: CharSequence?) {
        super.onPushCodeBlock(codeLexeme)

        val code = codeLexeme?.toString() ?: ""

        val type = typeStack.peek()

        if (code.contains("\n")) {
            val parts = code.split("\n")

            currentTokens.add(HlToken(type, parts[0]))

            for (part in parts.subList(1, parts.size)) {
                lines.add(HlLine("", currentTokens))
                currentTokens = mutableListOf(HlToken(type, part))
            }
        } else {
            currentTokens.add(HlToken(type, code))
        }
    }

    override fun onStart() {
        super.onStart()

        typeStack.push(HlTokenType.NORMAL)
    }

    override fun onFinish() {
        super.onFinish()

        if (!currentTokens.isEmpty()) {
            lines.add(HlLine("", currentTokens))
            currentTokens = mutableListOf()
        }
    }

//    override fun onAbort(code: CharSequence?) {
//        super.onAbort(code)
//
//        throw MarkdownException("render code failed: $code")
//    }

    fun codeBlock(): HlCodeBlock {
        return HlCodeBlock(lines)
    }
}

fun highlightCode(code: String, lang: String): HlCodeBlock {
    val render = HlRenderer()
    val highlighter = Highlighter { render }
    val result = if (lang.isBlank()) {
        highlighter.highlightAuto(code, null)
    } else {
        highlighter.highlight(lang, code)
    }

    return if (result.relevance == 0) {
        val lines = code.split("\n").map { HlLine("", listOf(HlToken(HlTokenType.NORMAL, it))) }
        HlCodeBlock(lines)
    } else {
        render.codeBlock()
    }
}
